﻿//--------------------------------------------------------------------------
// 
//  Copyright (c) Chili Software.  All rights reserved. 
// 
//  File: OracleCommandBuilder.cs
//
//  Description: Creates SQL commands for Oracle.
// 
//--------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Text;
using Chili.Opf3.Query;
using Chili.Opf3.Query.OPathParser;
using Chili.Opf3.Mapping;

namespace Chili.Opf3.Storages.Oracle
{
	/// <summary>
	/// Class that converts
	/// <see cref="Opf3.Query.ObjectExpression">ObjectExpression</see> classes to storage
	/// dependent <see cref="Opf3.Query.CompiledQuery">CompiledQuery</see> classes for Oracle.
	/// </summary>
	internal sealed class OracleCommandBuilder : SqlCommandBuilderBase
	{
		/// <summary>
		/// Creates a new instance of the <see cref="OracleCommandBuilder">OracleCommandBuilder Class</see>.
		/// </summary>
		public OracleCommandBuilder() : base(true)
		{
		}

		/// <summary>
		/// Returns a sequence name for autonumber columns.
		/// </summary>
		/// <param name="entity">Name of the entity.</param>
		/// <param name="field">Name of the field.</param>
		/// <param name="storage">The storage that the query is created for.</param>
		private string GetValidSequenceName(OracleStorage storage, string entity, string field)
		{
			return String.Format(storage.SequenceFormat + ".nextval", entity, field);
		}

		#region BuildInsertQuery

		/// <summary>
		/// Creates the <see cref="Opf3.Query.CompiledQuery">CompildQuery</see> that contains a storage dependent 
		/// update query by using the <see cref="Opf3.Query.PersistentObjectExpression">PersistentObjectExpression</see> given.
		/// </summary>
		/// <param name="poe">
		/// 	<see cref="Opf3.Query.PersistentObjectExpression">PersistentObjectExpression</see> that is converted
		/// to a <see cref="Opf3.Query.CompiledQuery">CompildQuery</see>.
		/// </param>
		/// <param name="sqlStorageBase">The storage that the query is compiled for.</param>
		/// <exception cref="ArgumentNullException">PersistentObjectExpression is null.</exception>
		/// <exception cref="ArgumentNullException">SqlStorageBase is null.</exception>
		protected override CompiledQuery BuildInsertQuery(SqlStorageBase sqlStorageBase, PersistentObjectExpression poe)
		{
			if (sqlStorageBase == null)
				throw new ArgumentNullException("sqlStorageBase");
			if (poe == null)
				throw new ArgumentNullException("poe");
			// Check if the storage implements the ICustomDataTypesStorage interface.
			ICustomDataTypesStorage customDataTypesStorage = sqlStorageBase as ICustomDataTypesStorage;

			// Create the command.
			IDbCommand command = sqlStorageBase.CreateCommand();
			CompiledQuery compiledQuery = new CompiledQuery(command, QueryTypes.Insert);
			// Set the timeout for the query.
			compiledQuery.Timeout = poe.Timeout;

			// Create the string builder for the query.
			StringBuilder fields = new StringBuilder();
			StringBuilder parameters = new StringBuilder();
			StringBuilder returning = new StringBuilder();
			StringBuilder returninginto = new StringBuilder();

			// Get the persistent, the mapping and the list of members.
			object persistent = poe.Persistent;
			IPartialPersist partial = persistent as IPartialPersist;
			TypeMapping mapping = TypeMapping.GetTypeMapping(persistent);
			IMemberInfoCollection members = mapping.Members;

			for (int i = 0; i < members.Count; i++)
			{
				IMemberInfo mi = members[i];
				FieldAttribute fa = mi.FieldAttribute;

				// Skip if the member should not be inserted.
				if ((fa.PersistBehaviours & PersistBehaviours.DontInsert) != 0)
					continue;
				// Skip members are not to persist.
				if (partial != null && !partial.IsMemberToPersist(mi.Name, PersistentOperations.Insert))
					continue;

				object value = mi.GetValue(persistent);
				// Convert the values back to the storage compatible values.
				if (customDataTypesStorage != null)
					value = customDataTypesStorage.ChangeToDataType(value);

				// Create the parameter for the storage.
				string parameterName = null;
				IDataParameter dataParameter = CreateParameter(sqlStorageBase, i, value, mi.MemberType, out parameterName);
				// Get the field name of the current member.
				string fieldName = mi.FieldAttribute.FieldName;

				fields.Append((fields.Length == 0 ? "" : ", ") + sqlStorageBase.GetValidFieldName(fieldName));

				if (fa.AutoNumber)
				{
					parameters.Append((parameters.Length == 0 ? "" : ", ") + GetValidSequenceName((OracleStorage)sqlStorageBase, mapping.PersistentAttribute.Entity, fieldName));
					returning.Append((returning.Length == 0 ? "" : ", ") + sqlStorageBase.GetValidFieldName(fieldName));
					returninginto.Append((returninginto.Length == 0 ? "" : ", ") + parameterName);
					dataParameter.Direction = ParameterDirection.Output;

					// Set the persistent attribute for the autonumbers.
					if (compiledQuery.AutoNumberItems == null)
						compiledQuery.AutoNumberItems = new AutoNumberItems(mapping.PersistentAttribute);
					// Add the field attribute to the fields.
					compiledQuery.AutoNumberItems.Add(new AutoNumberItem(fa));
				}
				else
				{
					parameters.Append((parameters.Length == 0 ? "" : ", ") + parameterName);
				}

				// Add the parameter to the list of parameters.
				command.Parameters.Add(dataParameter);
			}

			// Create the string builder that holds the final query.
			if (returning.Length == 0)
				command.CommandText = string.Format("insert into {0} ({1}) values ({2})", mapping.PersistentAttribute.Entity, fields, parameters);
			else
				command.CommandText = string.Format("insert into {0} ({1}) values ({2}) returning {3} into {4}", mapping.PersistentAttribute.Entity, fields, parameters, returning, returninginto);

			return compiledQuery;
		}

		#endregion
	}
}
